// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_WASM_JUMP_TABLE_ASSEMBLER_H_
#define V8_WASM_JUMP_TABLE_ASSEMBLER_H_

#include "src/macro-assembler.h"
#include "src/wasm/wasm-code-manager.h"

namespace v8 {
namespace internal {
    namespace wasm {

        // The jump table is the central dispatch point for all (direct and indirect)
        // invocations in WebAssembly. It holds one slot per function in a module, with
        // each slot containing a dispatch to the currently published {WasmCode} that
        // corresponds to the function.
        //
        // Note that the table is split into lines of fixed size, with lines laid out
        // consecutively within the executable memory of the {NativeModule}. The slots
        // in turn are consecutive within a line, but do not cross line boundaries.
        //
        //   +- L1 -------------------+ +- L2 -------------------+ +- L3 ...
        //   | S1 | S2 | ... | Sn | x | | S1 | S2 | ... | Sn | x | | S1  ...
        //   +------------------------+ +------------------------+ +---- ...
        //
        // The above illustrates jump table lines {Li} containing slots {Si} with each
        // line containing {n} slots and some padding {x} for alignment purposes.
        class V8_EXPORT_PRIVATE JumpTableAssembler : public MacroAssembler {
        public:
            // Translate an offset into the continuous jump table to a jump table index.
            static uint32_t SlotOffsetToIndex(uint32_t slot_offset)
            {
                uint32_t line_index = slot_offset / kJumpTableLineSize;
                uint32_t line_offset = slot_offset % kJumpTableLineSize;
                DCHECK_EQ(0, line_offset % kJumpTableSlotSize);
                return line_index * kJumpTableSlotsPerLine + line_offset / kJumpTableSlotSize;
            }

            // Translate a jump table index to an offset into the continuous jump table.
            static uint32_t SlotIndexToOffset(uint32_t slot_index)
            {
                uint32_t line_index = slot_index / kJumpTableSlotsPerLine;
                uint32_t line_offset = (slot_index % kJumpTableSlotsPerLine) * kJumpTableSlotSize;
                return line_index * kJumpTableLineSize + line_offset;
            }

            // Determine the size of a jump table containing the given number of slots.
            static constexpr uint32_t SizeForNumberOfSlots(uint32_t slot_count)
            {
                // TODO(wasm): Once the {RoundUp} utility handles non-powers of two values,
                // use: {RoundUp<kJumpTableSlotsPerLine>(slot_count) * kJumpTableLineSize}
                return ((slot_count + kJumpTableSlotsPerLine - 1) / kJumpTableSlotsPerLine) * kJumpTableLineSize;
            }

            // Translate a stub slot index to an offset into the continuous jump table.
            static uint32_t StubSlotIndexToOffset(uint32_t slot_index)
            {
                return slot_index * kJumpTableStubSlotSize;
            }

            // Determine the size of a jump table containing only runtime stub slots.
            static constexpr uint32_t SizeForNumberOfStubSlots(uint32_t slot_count)
            {
                return slot_count * kJumpTableStubSlotSize;
            }

            static void EmitLazyCompileJumpSlot(Address base, uint32_t slot_index,
                uint32_t func_index,
                Address lazy_compile_target,
                WasmCode::FlushICache flush_i_cache)
            {
                Address slot = base + SlotIndexToOffset(slot_index);
                JumpTableAssembler jtasm(slot);
                jtasm.EmitLazyCompileJumpSlot(func_index, lazy_compile_target);
                jtasm.NopBytes(kJumpTableSlotSize - jtasm.pc_offset());
                if (flush_i_cache) {
                    FlushInstructionCache(slot, kJumpTableSlotSize);
                }
            }

            static void EmitRuntimeStubSlot(Address base, uint32_t slot_index,
                Address builtin_target,
                WasmCode::FlushICache flush_i_cache)
            {
                Address slot = base + StubSlotIndexToOffset(slot_index);
                JumpTableAssembler jtasm(slot);
                jtasm.EmitRuntimeStubSlot(builtin_target);
                jtasm.NopBytes(kJumpTableStubSlotSize - jtasm.pc_offset());
                if (flush_i_cache) {
                    FlushInstructionCache(slot, kJumpTableStubSlotSize);
                }
            }

            static void PatchJumpTableSlot(Address base, uint32_t slot_index,
                Address new_target,
                WasmCode::FlushICache flush_i_cache)
            {
                Address slot = base + SlotIndexToOffset(slot_index);
                JumpTableAssembler jtasm(slot);
                jtasm.EmitJumpSlot(new_target);
                jtasm.NopBytes(kJumpTableSlotSize - jtasm.pc_offset());
                if (flush_i_cache) {
                    FlushInstructionCache(slot, kJumpTableSlotSize);
                }
            }

        private:
            // Instantiate a {JumpTableAssembler} for patching.
            explicit JumpTableAssembler(Address slot_addr, int size = 256)
                : MacroAssembler(nullptr, JumpTableAssemblerOptions(),
                    CodeObjectRequired::kNo,
                    ExternalAssemblerBuffer(
                        reinterpret_cast<uint8_t*>(slot_addr), size))
            {
            }

// To allow concurrent patching of the jump table entries, we need to ensure
// that the instruction containing the call target does not cross cache-line
// boundaries. The jump table line size has been chosen to satisfy this.
#if V8_TARGET_ARCH_X64
            static constexpr int kJumpTableLineSize = 64;
            static constexpr int kJumpTableSlotSize = 10;
            static constexpr int kJumpTableStubSlotSize = 18;
#elif V8_TARGET_ARCH_IA32
            static constexpr int kJumpTableLineSize = 64;
            static constexpr int kJumpTableSlotSize = 10;
            static constexpr int kJumpTableStubSlotSize = 10;
#elif V8_TARGET_ARCH_ARM
            static constexpr int kJumpTableLineSize = 5 * kInstrSize;
            static constexpr int kJumpTableSlotSize = 5 * kInstrSize;
            static constexpr int kJumpTableStubSlotSize = 5 * kInstrSize;
#elif V8_TARGET_ARCH_ARM64
            static constexpr int kJumpTableLineSize = 3 * kInstrSize;
            static constexpr int kJumpTableSlotSize = 3 * kInstrSize;
            static constexpr int kJumpTableStubSlotSize = 6 * kInstrSize;
#elif V8_TARGET_ARCH_S390X
            static constexpr int kJumpTableLineSize = 128;
            static constexpr int kJumpTableSlotSize = 20;
            static constexpr int kJumpTableStubSlotSize = 14;
#elif V8_TARGET_ARCH_PPC64
            static constexpr int kJumpTableLineSize = 64;
            static constexpr int kJumpTableSlotSize = 48;
            static constexpr int kJumpTableStubSlotSize = 7 * kInstrSize;
#elif V8_TARGET_ARCH_MIPS
            static constexpr int kJumpTableLineSize = 6 * kInstrSize;
            static constexpr int kJumpTableSlotSize = 6 * kInstrSize;
            static constexpr int kJumpTableStubSlotSize = 4 * kInstrSize;
#elif V8_TARGET_ARCH_MIPS64
            static constexpr int kJumpTableLineSize = 8 * kInstrSize;
            static constexpr int kJumpTableSlotSize = 8 * kInstrSize;
            static constexpr int kJumpTableStubSlotSize = 6 * kInstrSize;
#else
            static constexpr int kJumpTableLineSize = 1;
            static constexpr int kJumpTableSlotSize = 1;
            static constexpr int kJumpTableStubSlotSize = 1;
#endif

            static constexpr int kJumpTableSlotsPerLine = kJumpTableLineSize / kJumpTableSlotSize;

            // {JumpTableAssembler} is never used during snapshot generation, and its code
            // must be independent of the code range of any isolate anyway. Just ensure
            // that no relocation information is recorded, there is no buffer to store it
            // since it is instantiated in patching mode in existing code directly.
            static AssemblerOptions JumpTableAssemblerOptions()
            {
                AssemblerOptions options;
                options.disable_reloc_info_for_patching = true;
                return options;
            }

            void EmitLazyCompileJumpSlot(uint32_t func_index,
                Address lazy_compile_target);

            void EmitRuntimeStubSlot(Address builtin_target);

            void EmitJumpSlot(Address target);

            void NopBytes(int bytes);
        };

    } // namespace wasm
} // namespace internal
} // namespace v8

#endif // V8_WASM_JUMP_TABLE_ASSEMBLER_H_
